

import gov.nih.nlm.ncbi.www.soap.eutils.EFetchPubmedServiceStub.AbstractTextType;
import gov.nih.nlm.ncbi.www.soap.eutils.EFetchPubmedServiceStub.AbstractType;
import gov.nih.nlm.ncbi.www.soap.eutils.EFetchPubmedServiceStub.GeneSymbolListType;
import gov.nih.nlm.ncbi.www.soap.eutils.EFetchPubmedServiceStub.MedlineCitationType;
import gov.nih.nlm.ncbi.www.soap.eutils.EFetchPubmedServiceStub.MeshHeadingListType;
import gov.nih.nlm.ncbi.www.soap.eutils.EFetchPubmedServiceStub.MeshHeadingType;
import gov.nih.nlm.ncbi.www.soap.eutils.EFetchPubmedServiceStub.QualifierNameType;
import gov.nih.nlm.ncbi.www.soap.eutils.EUtilsServiceStub.DocSumType;
import gov.nih.nlm.ncbi.www.soap.eutils.EUtilsServiceStub.ItemType;

import java.io.UnsupportedEncodingException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.logging.Logger;

import org.apache.axis2.AxisFault;


/*
 * Author: Chengkun Wu (wchknudt@gmail.com)
 * This code requires apache axis 2 and support from NCBI, for details, please go to the descriptions
 * on Google code wiki of this project
 */
public class gnEUtils4j {
	public String searchTerm = "thyroid cancer";
	public String searchType = "PubMed";
	public int retMax = 50000;
	public Vector<String> idList = new Vector<String>();
	public static Logger logger = Logger.getLogger(gnEUtils4j.class.getName()); 
	public boolean logFlag = true;
	
	public void clearSearchTerm(){
		searchTerm = "";
	}
	
	public void limitMeshMajorTopic(String meshMajorTopic, String limitType){
		if (searchTerm.length() > 0) {
			searchTerm = "("+ searchTerm + ")";
			searchTerm +=  limitType; //"and", "or"
		}

		searchTerm += String.format(" \"%s\"[majr:noexp]", meshMajorTopic);
	}
	
	public void limitHasAbstract(){
		searchTerm = String.format("( %s ) AND hasabstract[text]", searchTerm);
	}

	public void limitMesh(String meshTerms, String limitType) {
		if (searchTerm.length() > 0) {
			searchTerm = "("+ searchTerm + ")";
			searchTerm +=  limitType; //"and", "or"
		}

		searchTerm += String.format(" \"%s\"[MeSH Terms]", meshTerms);
	}
	
	public void limitSuppConcept(String conceptName){
		searchTerm = conceptName;
	}
	
	public void limitExtra(String limit, String limitType){
		if (searchTerm.length() > 0) {
			searchTerm = "("+ searchTerm + ")";
			searchTerm +=  limitType; //"and", "or"
		}

		searchTerm += String.format(" \"%s\"", limit);
	}
	
	public void limitAllFields(String fieldLimit, String limitType){
		if (searchTerm.length() > 0) {
			searchTerm = "("+ searchTerm + ")";
			searchTerm +=  limitType; //"and", "or"
		}
		
		searchTerm += String.format(" \"%s\"[All Fields]", fieldLimit);
	}

	public void limitLang(String language) {

		if (searchTerm.length() > 0) {
			searchTerm += " AND ";
		}

		searchTerm += String.format(" %s[lang]", language);
	}

	public void limitPubDate(String startDate, String... endDate) {
		if (searchTerm.length() > 0) {
			if (searchTerm.length() > 0) {
				searchTerm = "("+ searchTerm + ")";
				searchTerm += " AND ";
			}
		}

		String limitToken = "PDAT";

		if (searchType.equalsIgnoreCase("pmc")) {
			limitToken = "PubDate";
		}

		searchTerm += String.format(" (\"%s\"[%s] :", startDate, limitToken);
		
		String endDateStr = "";
		if (endDate.length == 0)
			endDateStr = "2014";
		else
			endDateStr = endDate[0];
		
		searchTerm += String.format(" \"%s\"[%s] )", endDateStr, limitToken);

	//	System.out.println(searchTerm);
	}
	
	public static String convert2UTF8(String original) throws UnsupportedEncodingException{
		if(original==null)
			return "";
		
		byte[] utf8Bytes = original.getBytes("UTF8");
		byte[] defaultBytes = original.getBytes();

		String newStr = new String(utf8Bytes, "UTF8");
		    
		return newStr;
	}

	public Map<String, Article> getPubMedArticleMain(String pmids) {
		if (pmids == null){
			logger.info("@getPubMedArticleByID: null pmid");
			return null;
		}
		
		String WebEnv = "";
		String query_key = "";
		EFetchPubmedServiceStub.PubmedArticleType art = null;
		Map<String, Article> mapArticle = new HashMap<String, Article>();

		try {
			EFetchPubmedServiceStub service = new EFetchPubmedServiceStub();
			// call NCBI EFetch utility
			EFetchPubmedServiceStub.EFetchRequest req = new EFetchPubmedServiceStub.EFetchRequest();
			req.setWebEnv(WebEnv);
			req.setQuery_key(query_key);
			req.setId(pmids);
			
			EFetchPubmedServiceStub.EFetchResult res = service.run_eFetch(req);
			
			if (res == null){
				logger.info("@getPubMedArticleByID: null efetch result.");
				return null;
			}

			for (int i = 0; i < res.getPubmedArticleSet().getPubmedArticleSetChoice().length; i++) {
				Article article = new Article(pmids, "pubmed");
				
				art = res.getPubmedArticleSet().getPubmedArticleSetChoice()[i].getPubmedArticle();

				if (art != null) {

					MedlineCitationType cite = art.getMedlineCitation();
					article.affiliation = cite.getArticle().getAffiliation();
					
					if (cite ==null){
						logger.info("@getPubMedArticleByID: null citation information");
						return null;
					}
					
					String id_ext = cite.getPMID().getString();
					MeshHeadingListType mhlt = cite.getMeshHeadingList();
					
					if(mhlt!=null){
						for(MeshHeadingType mht : mhlt.getMeshHeading()){
							
							String termDesc = mht.getDescriptorName().getString();

							QualifierNameType[] termQual = mht.getQualifierName();
														
							if(termQual == null){
								String majorDescYN = mht.getDescriptorName().getMajorTopicYN().toString();
								if (majorDescYN.equalsIgnoreCase("Y"))
									termDesc += "*";
								article.mesh_terms.add(termDesc);
							}
							else{
								for(QualifierNameType qnt: termQual){
									String majorQualYN = qnt.getMajorTopicYN().toString();
									String fullTerm = termDesc + "/" + qnt.getString();
									if (majorQualYN.equalsIgnoreCase("Y"))
										fullTerm += "*";
									article.mesh_terms.add(fullTerm);
								}
							}
						}
					}
					
					String text_title = cite.getArticle().getArticleTitle().getString();
				//	System.out.println(text_title);
					AbstractType abstractTypeObj = cite.getArticle().getAbstract();
					
					String abstractText = "";
					if (abstractTypeObj != null) { 
						AbstractTextType[] abtexts = abstractTypeObj.getAbstractText();
						if(abtexts!=null){
							for(AbstractTextType ab : abtexts){
								abstractText+=ab.getString();
							}
						}
					}
					
					 
					article.id_ext = id_ext;
					article.text_title = convert2UTF8(text_title);
					article.text_abstract = convert2UTF8(abstractText);
					
				//	System.out.println(newAbstractText);
					
					mapArticle.put(article.id_ext, article);
				}
			}
			service.cleanup();
		} catch (Exception e) {
			System.out.println(e.toString());
		}
		
		return mapArticle;
	}
	
	public Map<String, Article> getPubmedArticleSummary(String pmids) throws UnsupportedEncodingException {
		if(pmids==null){
			logger.info("@getPubmedArticleTypes: null pmid.");
			return null;
		}

		EUtilsServiceStub service = null;
		try {
			service = new EUtilsServiceStub();
		} catch (AxisFault e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
		if (service == null){
			logger.info("@getPubmedArticleTypes: null service object.");
			return null;
		}

		Map<String, Article> mapArticle = new HashMap<String, Article>();
		EUtilsServiceStub.ESummaryRequest req = new EUtilsServiceStub.ESummaryRequest();
		req.setDb("pubmed");
		req.setId(pmids);
		EUtilsServiceStub.ESummaryResult res;
		
		try {
			res = service.run_eSummary(req);

			// results output
			if(res==null){
				logger.info("@getPubmedArticleTypes: null summary result.");
				return null;
			}
			
			DocSumType[] docs = res.getDocSum();
			
			if(docs!=null){
				for(int i=0; i<docs.length; i++)
				{
					Article article = new Article(pmids, "pubmed");
					article.id_ext = res.getDocSum()[i].getId();
					
					ItemType[] itemTypeArray = res.getDocSum()[i].getItem();
					for (int k = 0; k < itemTypeArray.length; k++)
					{
						String name = itemTypeArray[k].getName();
						
						if(name.equals("PubTypeList")){
							if(itemTypeArray[k]!=null){
								ItemType[] items = itemTypeArray[k].getItem();
								if(items!=null){
									for(ItemType item : items){
										String itemContent = convert2UTF8(item.getItemContent());
										article.pubTypeList.add(itemContent);
									}
								}
							}
						}
						
						if(name.equals("FullJournalName")){
							if(itemTypeArray[k]!=null){
								article.journal = convert2UTF8(itemTypeArray[k].getItemContent());
							}
						}
						
						if(name.equals("AuthorList")){
							if(itemTypeArray[k]!=null){
								ItemType[] items = itemTypeArray[k].getItem();
								if(items!=null){
									for(ItemType item : items){
										article.authors.add(convert2UTF8(item.getItemContent()));
									}
								}
							}
						}
						
						if(name.equals("PubDate")){
							if(itemTypeArray[k]!=null){
								String date = itemTypeArray[k].getItemContent();
								article.date = date;
							}
						}
						
					}
					mapArticle.put(article.id_ext, article);
				}
			}
			
			service.cleanup();
		} catch (RemoteException e) {
			e.printStackTrace();
		}		
		
		return mapArticle;
	}
	
	public  Map<String, Article> getPubMedArticle(String pmids) throws UnsupportedEncodingException {
		if (pmids.equals(""))
			return null;
		
		//Delay for some time according to the NCBI policy
		try {
			//System.out.println("Sleeping");
			Thread.sleep(4000);
			//System.out.println("Fetching");
		} catch (InterruptedException ie) {
			System.out.println(ie.getMessage());
		}
		
		Map<String, Article> mapArticleMain = getPubMedArticleMain(pmids);
		Map<String, Article> mapArticleSummary = getPubmedArticleSummary(pmids);
		
		String[] ids = pmids.replaceAll("\\s","").split(",");
		
		for(String id: ids){
			Article articleMain = null;
			if (mapArticleMain != null)
				articleMain = mapArticleMain.get(id);
			
			Article articleSummary = null;
			
			if (mapArticleSummary != null )
				articleSummary = mapArticleSummary.get(id);
			
			if (articleMain == null){
				System.out.println("Null articleMain found:" + id);
				continue;
			}
			
			if (articleSummary == null){
				System.out.println("Null articleSummary found:" + id);
				continue;
			}
			
			mapArticleMain.put(id, getPubMedArticle(articleMain, articleSummary));
		}
		
		return mapArticleMain;
	}
	
	public Article getPubMedArticle(Article articleMain, Article articleSummary){
		
		if(articleMain == null){
			System.out.println("Article Main empty!");
		}
		
		if(articleSummary == null){
			System.out.println("Article Summary Empty:" + articleMain.id_ext);
		}
		
		articleMain.authors = articleSummary.authors;
		articleMain.pubTypeList = articleSummary.pubTypeList;
		articleMain.journal = articleSummary.journal;
		articleMain.date = articleSummary.date;
		
		return articleMain;
	}

	public String setDefaultSearchTerm(){
		searchTerm = "";
		limitMeshMajorTopic("thyroid neoplasms", " AND ");
		//limitAllFields("thyroid cancer", " AND ");
		//limitAllFields("thyroid neoplasms", " OR ");
		this.limitMesh("humans", " and ");
		this.limitPubDate("1990");
		limitLang("English");
		limitHasAbstract();
		
		return searchTerm;
	}
	
	public String setDefaultSearchTerm(String searchTermStr){
	//	searchTerm = "";
		searchTerm = searchTermStr;
		limitMeshMajorTopic("thyroid neoplasms", " AND ");
		//limitAllFields("thyroid cancer", " AND ");
		//limitAllFields("thyroid neoplasms", " OR ");
		this.limitMesh("humans", " and ");
		this.limitPubDate("1990");
		limitLang("English");
		limitHasAbstract();
		
		return searchTerm;
	}
	
	public void eSearchQuery(String dbname) {
		EUtilsServiceStub.IdListType idlist = null;

		try {
			EUtilsServiceStub service = new EUtilsServiceStub();
			// call NCBI ESearch utility
			// NOTE: search term should be URL encoded
			EUtilsServiceStub.ESearchRequest req = new EUtilsServiceStub.ESearchRequest();
			req.setDb(dbname);

			req.setTerm(searchTerm);
			
			if(logFlag)
				logger.info(searchTerm);
			req.setRetMax(Integer.toString(retMax));
			
			EUtilsServiceStub.ESearchResult res = service.run_eSearch(req);
			
			if (res == null)
				return;

			if(logFlag)
				logger.info("Found ids: " + res.getCount());

		//	System.out.print("First " + res.getRetMax() + " ids: ");
			idlist = res.getIdList();
			if(idlist == null || idlist.getId()==null){
				idList = null;
				return;
			}
			idList.clear();
			
			for (int i = 0; i < idlist.getId().length; i++) {
			//	System.out.println( idlist.getId()[i]);
				idList.add(idlist.getId()[i]);
			}
			//System.out.println();
		} catch (Exception e) {
			e.printStackTrace();
		}

	}
	
	public List<String> eLinkQuery(String id) throws Exception {
		//System.out.println("Querying PubMed articles related to PMID:" + id);
		List<String> returnList = null;
		try {
			EUtilsServiceStub service = new EUtilsServiceStub();
			// call NCBI ELink utility
			EUtilsServiceStub.ELinkRequest req = new EUtilsServiceStub.ELinkRequest();
			req.setDb("pubmed");
			req.setDbfrom("pubmed");
			
			
			req.setId(new String[] { id });
			
			EUtilsServiceStub.ELinkResult res = service.run_eLink(req);

			// results output
			for (int i = 0; i < res.getLinkSet().length; i++) {
				List<String> resList = new ArrayList<String>();
				String linkStart = "";

				/*
				 * System.out.print("Links from " +
				 * res.getLinkSet()[i].getDbFrom()); System.out.println(" to " +
				 * res.getLinkSet()[i].getLinkSetDb()[0].getDbTo());
				 * System.out.print("  " + res.getLinkSet()[i].getDbFrom() +
				 * " id(s): ");
				 */

				for (int k = 0; k < res.getLinkSet()[i].getIdList().getId().length; k++) {
					linkStart = res.getLinkSet()[i].getIdList().getId()[k]
							.getString();
					// System.out.print(linkStart + " ");
				}
				// System.out.println();
				// System.out.print("  " +
				// res.getLinkSet()[i].getLinkSetDb()[0].getDbTo() +
				// " id(s): ");
				for (int k = 0; k < res.getLinkSet()[i].getLinkSetDb()[0]
						.getLink().length; k++) {
					String linkEnd = res.getLinkSet()[i].getLinkSetDb()[0]
							.getLink()[k].getId().getString();
					// System.out.print( linkEnd + " ");
					if (!linkEnd.equals(linkStart)){
						resList.add(linkEnd);
					}
				}
				// System.out.println("\n----------------------");
				returnList = resList;
			}

		} catch (Exception e) {
			System.out.println(e.toString());
		}
		
		int size = 0;
		if (returnList != null)
			size = returnList.size();
		
	//	System.out.println(size + " related articles retrieved!");

		return returnList;
    }

	public static void main(String[] args) throws Exception {
		gnEUtils4j client = new gnEUtils4j();

/*		client.setDefaultSearchTerm();
		
	//	System.out.println(client.searchTerm);
	//	client.eSearchQuery("pubmed");
		
		
		Map<String, Article> mapArticle = client.getPubMedArticle("22335355");
		
		
		for(Article art: mapArticle.values()){
			System.out.println(art.toString() + "\n\n");
		}*/
		
		//Article article = client.getPubMedArticle(client.idList.get(0));
		//System.out.println(article.toString());
		
		//List resList = client.eLinkQuery("16604553");
		//System.out.println("Size of related articles: " + resList.size());
//		client.searchTerm = "\"Thyroid cancer, follicular\" [Supplementary Concept]";
//		client.eSearchQuery("pubmed");
		//client.getPubMedArticle("22481925");
		client.getPubmedArticleSummary("21116648");
	}
}